package com.michaelbulava.TFS.tpignore;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.michaelbulava.TFS.log.FileLogger;
import com.microsoft.tfs.util.Check;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

/**
 * Created by mbulava on 6/22/2015.
 */
public final class TPIgnoreCache {
    private static final Logger log = FileLogger.getInstance(TPIgnoreCache.class);
    private static final Pattern[] NO_MATCH_PATTERN_ARRAY = new Pattern[0];
    private final HashMap<VirtualFile, TPIgnoreCacheEntry> ignoreFileToEntriesMap = new HashMap<VirtualFile, TPIgnoreCacheEntry>();
    private final Module myModule;


    @Deprecated
    public static VirtualFile getIgnoreFile(FilePath file, Module module){
        return getIgnoreFile(file.getVirtualFile(), module);
    }

    @Deprecated
    public static VirtualFile getIgnoreFile(VirtualFile file, Module module){
        return getIgnoreFile(module);
    }

    public static VirtualFile getIgnoreFile(Module module){
        Check.notNull(module, "module");

        String modulePath = module.getModuleFile().getParent().getPath();
        File ioFile = new File(modulePath, TPIgnoreDocument.DEFAULT_FILENAME);

        VirtualFile ignoreFile = LocalFileSystem.getInstance().findFileByIoFile(ioFile);
        if (ignoreFile == null && ioFile.exists())
            ignoreFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ioFile);
        return ignoreFile;
    }



    public static String createIgnorePatternForVirtualFile(VirtualFile file, Module module){
        Check.notNull(file, "file");
        String path = createPathMatchString(file, module);

//        String patternString = Pattern.quote(path);
//        if (file.isDirectory())
//            patternString = patternString + ".*";
        return path;
    }

    public static String createPathMatchString(VirtualFile file, Module module){
//        Module module = TFSFileUtil.getModuleForFile(file, project);
//        if (module == null)
//            return null;
        Check.notNull(module, "module");

        String matchPath = FileUtil.getRelativePath(module.getModuleFile().getParent().getPath(),
                file.getPath(),'/');
        if (!matchPath.startsWith("/"))
            matchPath = "/" + matchPath;

        if (file.isDirectory() && !matchPath.endsWith("/"))
            matchPath = matchPath + "/";
        return matchPath;
    }

    public TPIgnoreCache(Module module){
        Check.notNull(module, "module");
        myModule = module;
    }

    public static String[] getPatternStrings(Collection<Pattern> patterns) {
        Check.notNull(patterns, "patterns");

        String[] ret = new String[patterns.size()];

        int i =0;
        for(Pattern pattern : patterns){
            ret[i++]  = pattern.toString();
        }
        return ret;
    }

    public Pattern[] getMatchingPatterns(VirtualFile file){
        Check.notNull(file, "file");
        if(canMatch(file)){
            Pattern[] patterns = getExclusionPatterns();
            if (patterns == null || patterns.length == 0)
                return NO_MATCH_PATTERN_ARRAY;

            String matchPath = createPathMatchString(file, myModule);

            Set<Pattern> matches = new TreeSet<>(new TPIgnorePatternComparator());
            for (Pattern pattern : patterns) {
                if (pattern.matcher(matchPath).matches()) {
                    log.debug(MessageFormat.format("item ''{0}'' matched exclusion pattern ''{1}''", new Object[]{matchPath, pattern.toString()}));
                    matches.add(pattern);
                }
            }
            if (matches.size() > 0) {
                return (Pattern[])matches.toArray(new Pattern[matches.size()]);
            }
        }
        return NO_MATCH_PATTERN_ARRAY;
    }

    public boolean matchesAnyPattern(VirtualFile file){
        Check.notNull(file, "resource");
        if (canMatch(file))
        {
            Pattern[] patterns = getExclusionPatterns();
            if ((patterns == null) || (patterns.length == 0)) {
                return false;
            }
            String matchPath = createPathMatchString(file, myModule);
            //String matchPath = createResourceMatchString(file);
            for (Pattern pattern : patterns) {
                if (pattern.matcher(matchPath).find()) {
                    log.info(MessageFormat.format("item ''{0}'' matched exclusion pattern ''{1}''", new Object[] { matchPath, pattern.toString() }));
                    return true;
                }
            }
        }
        return false;
    }


    private boolean canMatch(VirtualFile file){
        return file.exists();
    }

    private Pattern[] getExclusionPatterns(){
        //Check.notNull(module, "module");

        VirtualFile ignoreFile = getIgnoreFile(myModule.getModuleFile(), myModule);
        if (ignoreFile == null)
            return null;
        boolean ignoreFileExists = ignoreFile.exists();
        long ignoreFileLastModified = ignoreFile.getModificationStamp();

        Pattern[] ret = null;
        synchronized (this.ignoreFileToEntriesMap){
            boolean alreadHaveEntry = ignoreFileToEntriesMap.containsKey(ignoreFile);

            TPIgnoreCacheEntry existingEntry = null;
            int refreshAction = RefreshAction.NOTHING;
            if (ignoreFileExists && alreadHaveEntry){
                existingEntry = this.ignoreFileToEntriesMap.get(ignoreFile);
                if (existingEntry.getLoadedFromDiskTime() != ignoreFileLastModified)
                    refreshAction = RefreshAction.LOAD_OR_REFRESH;
                else
                    refreshAction = RefreshAction.RETURN_EXISTING;
            } else if (ignoreFileExists && !alreadHaveEntry){
                refreshAction = RefreshAction.LOAD_OR_REFRESH;
            } else if (!ignoreFileExists && alreadHaveEntry){
                refreshAction = RefreshAction.REMOVE_EXISTING;
            }

            switch (refreshAction){
                case 2:
                    //log.info("LOAD OR REFRESH");
                    ret = TPIgnoreFileParser.load(ignoreFile);

                    if (ret == null)
                        ignoreFileToEntriesMap.remove(ignoreFile);
                    else
                        ignoreFileToEntriesMap.put(ignoreFile, new TPIgnoreCacheEntry(ignoreFile.getModificationStamp(),
                                ret));
                    break;
                case 3:
                    //log.info("REMOVE EXISTING");
                    ignoreFileToEntriesMap.remove(ignoreFile);
                    break;
                case 4:
                    //log.info("RETURN EXISTING");
                    Check.notNull(existingEntry, "existingEntry");
                    ret = existingEntry.getPatterns();
                    break;
                case 1:
                    //log.info("NOTHING ");
            }
            return ret;
        }
    }

    private static class RefreshAction
    {
        public static final int NOTHING = 1;
        public static final int LOAD_OR_REFRESH = 2;
        public static final int REMOVE_EXISTING = 3;
        public static final int RETURN_EXISTING = 4;
    }
}
